/*
 * 
 *  This file is part of Sourceprojects.org Lycia Copyright 2010 noctarius
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 * 
 */
package org.sourceprojects.lycia.internal;

import java.io.FilterReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.CharBuffer;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;

import org.sourceprojects.lycia.ProgressListener;
import org.sourceprojects.lycia.internal.logging.LyciaLoggerWrapper;

/**
 * A {@link Reader} implementation to update status on registered
 * {@link ProgressListener}s.
 * 
 * @author noctarius
 * @since 0.0.7
 */
public class ProgressAwareReader extends FilterReader {
	private static final LyciaLoggerWrapper LOGGER = LyciaLoggerWrapper
			.getLogger(ProgressAwareReader.class);

	private final ReentrantLock lock = new ReentrantLock();

	private final List<ProgressListener> listeners;
	private final long fileLength;
	private int lastPercent = 0;
	private long position = 0;
	private long markedPosition = 0;

	public ProgressAwareReader(final InputStream inputStream,
			final List<ProgressListener> listeners) throws IOException {

		super(new InputStreamReader(inputStream));

		if (inputStream instanceof DataLength) {
			this.fileLength = ((DataLength) inputStream).length();
		} else {
			this.fileLength = inputStream.available();
		}

		this.listeners = listeners;
	}

	@Override
	public void mark(final int readAheadLimit) throws IOException {
		if (super.markSupported()) {
			markedPosition = readAheadLimit;
			super.mark(readAheadLimit);
		}
	}

	@Override
	public int read(final char[] cbuf, final int off, final int len)
			throws IOException {

		final int length = super.read(cbuf, off, len);
		notifyListeners(length);
		return length;
	}

	@Override
	public int read() throws IOException {
		notifyListeners(4);
		return super.read();
	}

	@Override
	public int read(final char[] cbuf) throws IOException {
		notifyListeners(cbuf.length);
		return super.read(cbuf);
	}

	@Override
	public int read(final CharBuffer target) throws IOException {
		notifyListeners(target.length());
		return super.read(target);
	}

	@Override
	public void reset() throws IOException {
		notifyListenersAboutReset();
		super.reset();
	}

	@Override
	public long skip(final long n) throws IOException {
		notifyListeners((int) n);
		return super.skip(n);
	}

	private void notifyListeners(final int bytes) {
		notifyListeners(bytes, position);
	}

	private void notifyListeners(final int bytes, final long offset) {
		if (fileLength == -1) {
			return;
		}

		lock.lock();
		try {
			position = offset + bytes;

			final Double percent = (position * 100.) / fileLength;
			if (percent.intValue() > lastPercent) {
				if (LOGGER.isDebugEnabled()) {
					LOGGER.debug("Value changed: " + percent.intValue());
				}

				lastPercent = percent.intValue();

				for (final ProgressListener listener : listeners) {
					listener.notify(lastPercent);
				}
			}
		} finally {
			lock.unlock();
		}
	}

	private void notifyListenersAboutReset() {
		if (fileLength == -1) {
			return;
		}

		lock.lock();
		try {
			position = markedPosition;

			final Double percent = (position * 100.) / fileLength;
			if (percent.intValue() > lastPercent) {
				if (LOGGER.isDebugEnabled()) {
					LOGGER.debug("Percent value resetted: "
							+ percent.intValue());
				}

				lastPercent = percent.intValue();

				for (final ProgressListener listener : listeners) {
					listener.notifyReset();
				}
			}
		} finally {
			lock.unlock();
		}
	}
}
